/*
* Copyright (c) Huawei Technologies Co., Ltd. 2019-2022. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Description:
* Author: huawei
* Create: 2019-10-15
*/

#include <linux/string.h>
#include <linux/slab.h>
#include <linux/errno.h>

#include "dms_define.h"
#include "dms_kv.h"
struct dms_kv_table g_kv_cb = {0};

#define CHECK_ARG(key, data, data_len, ret)                                           \
    do {                                                                              \
        if (((key) == NULL) || ((data) == NULL) || ((data_len) > KV_VALUE_MAX_LEN)   \
            || ((data_len) <= 0)) {                                                   \
            dms_err("Invaild Parameter.\n");                                          \
            return -EINVAL;                                                           \
        }                                                                             \
        if (strnlen(key, KV_KEY_MAX_LEN) >= KV_KEY_MAX_LEN) {                         \
            dms_err("Key too long. (max=%d)\n", KV_KEY_MAX_LEN);                      \
            return -EINVAL;                                                           \
        }                                                                             \
    } while (0)
static void free_kv(struct dms_kv_node* kv)
{
    if (kv != NULL) {
        kfree(kv->data);
        kv->data = NULL;

        kfree(kv->key);
        kv->key = NULL;
        kfree(kv);
    }
}

static inline unsigned int dms_kv_hash_33(const char* tag)
{
    unsigned int hash = 0;
    while (*tag) {
        hash = (hash << 5) + hash + *tag++; /* 5 hash left offset */
    }
    return hash;
}

static inline struct hlist_head* dms_kv_get_table_head(const char* key)
{
    return &g_kv_cb.table[dms_kv_hash_33(key) % KV_TABLE_SIZE];
}

static struct dms_kv_node* dms_kv_new_node(const char* key, unsigned int key_len, void* value, int len)
{
    struct dms_kv_node* node = NULL;
    int ret;
    node = kzalloc(sizeof(struct dms_kv_node), GFP_KERNEL | __GFP_ACCOUNT);
    if (node == NULL) {
        dms_err("kmalloc failed. (size=%lu; key=\"%s\")\n", sizeof(struct dms_kv_node), key);
        return NULL;
    }
    node->key = kzalloc(sizeof(char) * (key_len + 1), GFP_KERNEL | __GFP_ACCOUNT);
    if (node->key == NULL) {
        dms_err("kmalloc failed. (size=%lu; key=\"%s\")\n", (sizeof(char) * (key_len + 1)), key);
        goto free_node;
    }
    ret = strcpy_s(node->key, (key_len + 1), key);
    if (ret != 0) {
        dms_err("strcpy failed. (ret=%d; size=%lu; key=\"%s\")\n", ret, strlen(key), key);
        goto free_key;
    }

    node->data = kzalloc(len, GFP_KERNEL | __GFP_ACCOUNT);
    if (node->data == NULL) {
        dms_err("kmalloc failed. (size=%d; key=\"%s\")\n", len, key);
        goto free_key;
    }
    ret = memcpy_s(node->data, len, value, len);
    if (ret != 0) {
        dms_err("memcpy failed. (ret=%d; size=%d; key=\"%s\")\n", ret, len, key);
        goto free_data;
    }
    node->data_len = len;
    return node;
free_data:
    kfree(node->data);
    node->data = NULL;
free_key:
    kfree(node->key);
    node->key = NULL;
free_node:
    kfree(node);
    node = NULL;
    return NULL;
}

static struct dms_kv_node* dms_kv_search(const char* key)
{
    struct hlist_head* table_head = NULL;
    struct dms_kv_node* node = NULL;
    struct hlist_node* tmp = NULL;
    table_head = dms_kv_get_table_head(key);
    hlist_for_each_entry_safe(node, tmp, table_head, hnode)
    {
        if (strncmp(node->key, key, strlen(key)) == 0) {
            break;
        }
    }
    return node;
}

static int dms_kv_add(const char* key, void* value, int len)
{
    struct hlist_head* table_head = NULL;
    struct dms_kv_node* node = NULL;

    node = dms_kv_new_node(key, strlen(key), value, len);
    if (node == NULL) {
        dms_err("Create new node failed. (key=\"%s\")\n", key);
        return -ENOMEM;
    }
    table_head = dms_kv_get_table_head(key);
    hlist_add_head(&node->hnode, table_head);
    return 0;
}

int dms_kv_del(const char* key)
{
    struct dms_kv_node* node = NULL;
    if (key == NULL) {
        dms_err("Invaild Parameter.\n");
        return -EINVAL;
    }
    mutex_lock(&g_kv_cb.lock);
    node = dms_kv_search(key);
    if (node != NULL) {
        hlist_del(&node->hnode);
        mutex_unlock(&g_kv_cb.lock);
        free_kv(node);
        return 0;
    }
    mutex_unlock(&g_kv_cb.lock);
    dms_err("No such device node. (key=\"%s\")\n", key);
    return -ENODEV;
}
int dms_kv_set(const char* key, void* value, int len)
{
    struct hlist_head* table_head = NULL;
    struct dms_kv_node* node = NULL;
    struct hlist_node* tmp = NULL;
    int ret;
    CHECK_ARG(key, value, len, -EINVAL);
    mutex_lock(&g_kv_cb.lock);
    table_head = dms_kv_get_table_head(key);
    hlist_for_each_entry_safe(node, tmp, table_head, hnode)
    {
        if (strcmp(node->key, key) == 0) {
            mutex_unlock(&g_kv_cb.lock);
            dms_err("Repeated registration of the same node, (key=\"%s\")\n", key);
            return -EINVAL;
        }
    }
    ret = dms_kv_add(key, value, len);
    mutex_unlock(&g_kv_cb.lock);
    return ret;
}

int dms_kv_get(const char* key, void* value, int len)
{
    struct dms_kv_node* node = NULL;
    int ret;
    CHECK_ARG(key, value, len, -EINVAL);
    mutex_lock(&g_kv_cb.lock);
    node = dms_kv_search(key);
    if (node != NULL) {
        ret = memcpy_s(value, len, node->data, node->data_len);
        mutex_unlock(&g_kv_cb.lock);
        return ret;
    }
    mutex_unlock(&g_kv_cb.lock);
    dms_err("No such device node. (key=\"%s\")\n", key);
    return -ENODEV;
}

void dms_kv_dump(char *buf, ssize_t *offset, key_dump_handle handle)
{
    struct hlist_head* table_head = NULL;
    struct dms_kv_node* node = NULL;
    struct hlist_node* tmp = NULL;
    int i;
    ssize_t ret;
    mutex_lock(&g_kv_cb.lock);
    for (i = 0; i < KV_TABLE_SIZE; i++) {
        table_head = &g_kv_cb.table[i];
        hlist_for_each_entry_safe(node, tmp, table_head, hnode)
        {
            ret = handle(node->data, buf, offset);
            if (ret < 0) {
                mutex_unlock(&g_kv_cb.lock);
                return;
            }
        }
    }
    mutex_unlock(&g_kv_cb.lock);
}


int dms_kv_init(void)
{
    int i;
    g_kv_cb.table = kzalloc(sizeof(struct hlist_head) * KV_TABLE_SIZE, GFP_KERNEL | __GFP_ACCOUNT);
    if (g_kv_cb.table == NULL) {
        dms_err("kmalloc failed. (size=%lu)\n", (sizeof(struct hlist_head) * KV_TABLE_SIZE));
        return -ENOMEM;
    }
    for (i = 0; i < KV_TABLE_SIZE; i++) {
        INIT_HLIST_HEAD(&g_kv_cb.table[i]);
    }
    mutex_init(&g_kv_cb.lock);
    return 0;
}

void dms_kv_uninit(void)
{
    struct hlist_head* table_head = NULL;
    struct dms_kv_node* node = NULL;
    struct hlist_node* tmp = NULL;
    int i;
    mutex_lock(&g_kv_cb.lock);
    for (i = 0; i < KV_TABLE_SIZE; i++) {
        table_head = &g_kv_cb.table[i];
        hlist_for_each_entry_safe(node, tmp, table_head, hnode)
        {
            hlist_del_init(&node->hnode);
            free_kv(node);
            node = NULL;
        }
    }
    mutex_unlock(&g_kv_cb.lock);
    mutex_destroy(&g_kv_cb.lock);
    kfree(g_kv_cb.table);
    g_kv_cb.table = NULL;
    return;
}
